home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue32 / sweetd / FILEREGX.PAS < prev   
Encoding:
Pascal/Delphi Source File  |  1997-12-07  |  20.4 KB  |  670 lines

  1. {*********************************************************}
  2. {* FileRegX                                              *}
  3. {* Copyright (c) Julian M Bucknall 1997                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Regular expression routines for filename matching     *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit FileRegX;
  14.  
  15. {$IFOPT D+}
  16. {$DEFINE Debug}
  17. {$ENDIF}
  18.  
  19. interface
  20.  
  21. uses
  22.   SysUtils;
  23.  
  24. const
  25.   c_frxAnyChar    = '?'; {match any character}
  26.   c_frxClosure    = '*'; {match zero or more characters}
  27.   c_frxPatClosure = '+'; {match zero or more subpatterns}
  28.   c_frxEscape     = '\'; {escape character}
  29.   c_frxClassLeft  = '['; {char class left bracket}
  30.   c_frxClassRight = ']'; {char class right bracket}
  31.   c_frxNegate     = '^'; {char class negation}
  32.   c_frxClassRange = '-'; {char class range character}
  33.  
  34. type
  35.   TfrxCompileResult = (   {possible compiler result codes}
  36.        frxcrSuccess,      {..success, no errors}
  37.        frxcrNoPattern,    {..no pattern to compile}
  38.        frxcrNoSubpattern, {..no subpattern for the + closure}
  39.        frxcrMissingChar,  {..no literal char for \ escape char}
  40.        frxcrMissingLeft,  {..no left bracket for right one}
  41.        frxcrBadClass);    {..badly formed class definition}
  42.  
  43. type
  44.   PfrxBinPattern = pointer;
  45.  
  46. function FRXCompilePattern(const aPattern    : string;
  47.                              var aBinPattern : PfrxBinPattern) : TfrxCompileResult;
  48.   {-compiles a pattern string into a binary pattern; returns result of
  49.     compilation: if successful aBinPattern is the binary pattern,
  50.     otherwise it's set to nil}
  51.  
  52. procedure FRXFreeBinPattern(var aBinPattern : PfrxBinPattern);
  53.   {-free a binary pattern created by FRXCompilePattern}
  54.  
  55. function FRXMatchesPattern(aBinPattern : PfrxBinPattern;
  56.                      const aFileName   : string) : boolean;
  57.   {-given a binary pattern and a file name, returns whether the file
  58.     name matches the pattern}
  59.  
  60. {$IFDEF Debug}
  61. procedure FRXPrintBinPattern(var aFile       : text;
  62.                            const aPattern    : string;
  63.                                  aBinPattern : PfrxBinPattern);
  64.   {-DEBUG only: prints a binary pattern to an open text file}
  65. {$ENDIF}
  66.  
  67. implementation
  68.  
  69. uses
  70.   {$IFDEF Windows}
  71.   WinProcs;
  72.   {$ELSE}
  73.   Windows;
  74.   {$ENDIF}
  75.  
  76. type
  77.   {$IFDEF Windows}
  78.   TMemSize = word;
  79.   {$ELSE}
  80.   TMemSize = integer;
  81.   {$ENDIF}
  82.  
  83. type
  84.   TCharSet = set of char;
  85.  
  86.   TTokenType = (c_binAnyChar,       {?}
  87.                 c_binAnyClosure,    {* or ?+}
  88.                 c_binLiteral,       {<char>}
  89.                 c_binLitClosure,    {<char>+}
  90.                 c_binClass,         {[...]}
  91.                 c_binClsClosure);   {[...]+}
  92.  
  93.   PBinPatNode = ^TBinPatNode;
  94.   TBinPatNode = packed record
  95.     bpnNext      : PBinPatNode;
  96.     bpnToken     : TTokenType;
  97.     bpnChar      : char;
  98.     bpnFiller    : word;
  99.     bpnCharClass : TCharSet;
  100.   end;
  101.  
  102.   PBinPatHeader = ^TBinPatHeader;
  103.   TBinPatHeader = packed record
  104.     bphSize : TMemSize;
  105.     bphNext : TMemSize;
  106.     bphData : PByteArray;
  107.   end;
  108.  
  109. const
  110.   XPNormalSize = sizeof(TBinPatNode) - sizeof(TCharSet);
  111.   XPClassSize  = sizeof(TBinPatNode);
  112.  
  113. const
  114.   c_BinPatBlockDelta = 512;
  115.  
  116. {===Binary pattern memory routines===================================}
  117. function NewBinaryPattern : PfrxBinPattern;
  118. begin
  119.   GetMem(Result, sizeof(TBinPatHeader));
  120.   with PBinPatHeader(Result)^ do begin
  121.     bphSize := c_BinPatBlockDelta;
  122.     bphNext := 0;
  123.     GetMem(bphData, c_BinPatBlockDelta);
  124.   end;
  125. end;
  126. {--------}
  127. function AllocPatternNode(var aBP        : PfrxBinPattern;
  128.                               aTokenType : TTokenType) : PBinPatNode;
  129. var
  130.   BPHdr    : PBinPatHeader absolute aBP;
  131.   ReqBytes : integer;
  132.   Temp     : PBinPatNode;
  133.   Dad      : PBinPatNode;
  134.   IsFirst  : boolean;
  135. begin
  136.   {if the binary pattern has not yet been allocated then do so}
  137.   IsFirst := false;
  138.   if (aBP = nil) then begin
  139.     aBP := NewBinaryPattern;
  140.     IsFirst := true;
  141.   end;
  142.  
  143.   {calculate the number of bytes required}
  144.   if (aTokenType = c_binClass) then
  145.     ReqBytes := XPClassSize
  146.   else
  147.     ReqBytes := XPNormalSize;
  148.  
  149.   {do we have enough room? if not realloc our binary pattern}
  150.   with BPHdr^ do begin
  151.     if ((bphSize - bphNext) < ReqBytes) then begin
  152.       {$IFDEF Windows}
  153.       ReallocMem(bphData, bphSize, bphSize + c_BinPatBlockDelta);
  154.       {$ELSE}
  155.       ReallocMem(bphData, bphSize + c_BinPatBlockDelta);
  156.       {$ENDIF}
  157.       inc(bphSize, c_BinPatBlockDelta);
  158.     end;
  159.   end;
  160.   {allocate the next node, set its fields}
  161.   with BPHdr^ do begin
  162.     Result := PBinPatNode(@bphData^[bphNext]);
  163.     inc(bphNext, ReqBytes);
  164.   end;
  165.   FillChar(Result^, ReqBytes, 0);
  166.   Result^.bpnToken := aTokenType;
  167.  
  168.   {if it wasn't the first node, make sure it's linked to the others}
  169.   if not IsFirst then begin
  170.     Temp := PBinPatNode(BPHdr^.bphData);
  171.     repeat
  172.       Dad := Temp;
  173.       Temp := Temp^.bpnNext;
  174.     until (Temp = nil);
  175.     Dad^.bpnNext := Result;
  176.   end;
  177. end;
  178. {====================================================================}
  179.  
  180. {===Helper routines==================================================}
  181. function LowerCaseChar(aCh : char) : char;
  182. {Convert a character to lowercase using language driver}
  183. begin
  184.   {$IFDEF Windows}
  185.   Result := char(AnsiLower(pointer(longint(aCh))));
  186.   {$ELSE}
  187.   Result := char(CharLower(pointer(longint(aCh))));
  188.   {$ENDIF}
  189. end;
  190. {--------}
  191. procedure NegateSet(var S : TCharSet);
  192. {Negate a character set}
  193. var
  194.   BA : TByteArray absolute S;
  195.   i  : integer;
  196. begin
  197.   for i := 0 to pred(sizeof(S)) do
  198.     BA[i] := not BA[i];
  199. end;
  200. {--------}
  201. function CloseLastPatternToken(aBinPattern : PfrxBinPattern) : boolean;
  202. {Given a binary pattern, attempts to close the last node, returns true
  203.  if successful}
  204. var
  205.   Temp : PBinPatNode;
  206.   Dad  : PBinPatNode;
  207. begin
  208.   {can't be successful if there is no binary pattern}
  209.   if (aBinPattern = nil) then begin
  210.     Result := false;
  211.     Exit;
  212.   end;
  213.   {find the last node}
  214.   Temp := PBinPatNode(PBinPatHeader(aBinPattern)^.bphData);
  215.   repeat
  216.     Dad := Temp;
  217.     Temp := Temp^.bpnNext;
  218.   until (Temp = nil);
  219.   {close it}
  220.   Result := true;
  221.   case Dad^.bpnToken of
  222.     c_binAnyChar : Dad^.bpnToken := c_binAnyClosure;
  223.     c_binLiteral : Dad^.bpnToken := c_binLitClosure;
  224.     c_binClass   : Dad^.bpnToken := c_binClsClosure;
  225.   else
  226.     {oops, already closed}
  227.     Result := false;
  228.   end;{case}
  229. end;
  230. {--------}
  231. function MatchOneChar(aToken : PBinPatNode;
  232.                       aCh    : char) : boolean;
  233. {Given a pattern token and a character, returns true if the char
  234.  matches the token}
  235. begin
  236.   case aToken^.bpnToken of
  237.     c_binAnyChar,
  238.     c_binAnyClosure : Result := true;
  239.     c_binLiteral,
  240.     c_binLitClosure : Result := aToken^.bpnChar = LowerCaseChar(aCh);
  241.     c_binClass,
  242.     c_binClsClosure : Result := LowerCaseChar(aCh) in aToken^.bpnCharClass;
  243.   else
  244.     Result := false;
  245.   end;
  246. end;
  247. {--------}
  248. function ParseCharClass(const aPattern : string;
  249.                               aPatLen  : integer;
  250.                           var aInx     : integer;
  251.                               aToken   : PBinPatNode) : TfrxCompileResult;
  252. {Parses a character class definition from a pattern string into a
  253.  pattern node; returns error code if any error encountered}
  254. type
  255.   TRangeState = (CouldStart, Started, Completed);
  256. var
  257.   FirstInx : integer;
  258.   FirstChar: char;
  259.   Ch       : char;
  260.   ChInx    : char;
  261.   NegatedClass      : boolean;
  262.   FoundRightBracket : boolean;
  263.   RangeState        : TRangeState;
  264. begin
  265.   {Input:  aPattern is the pattern string
  266.            aPatLen is its length
  267.            aInx is the position of the left bracket
  268.            aToken is the new token to hold the class definition
  269.    Output: Result is the error code
  270.            aInx is the position of the right bracket if successful
  271.            aToken has the class definition}
  272.  
  273.   {assume we fail}
  274.   Result := frxcrBadClass;
  275.  
  276.   {assume that the class is not negated, and we shall not find the
  277.    right bracket, and that ranges are complete}
  278.   NegatedClass := false;
  279.   FoundRightBracket := false;
  280.   RangeState := Completed;
  281.  
  282.   {fool compiler hints/warnings}
  283.   FirstChar := #0;
  284.  
  285.   {wander through the pattern string character by character}
  286.   FirstInx := succ(aInx);
  287.   while (aInx < aPatLen) do begin
  288.     inc(aInx);
  289.  
  290.     {look for a char class metacharacter}
  291.     Ch := LowerCaseChar(aPattern[aInx]);
  292.     case Ch of
  293.  
  294.       c_frxEscape :
  295.         begin
  296.           {the escape character cannot be the last character}
  297.           if (aInx = aPatLen) then
  298.             Exit;
  299.           inc(aInx);
  300.           Ch := LowerCaseChar(aPattern[aInx]);
  301.           {it's now a literal character; there are two cases, it's the
  302.            end of a range or it isn't}
  303.           if (RangeState = Started) then begin
  304.             if (Ch <= FirstChar) then
  305.               Exit;
  306.             for ChInx := succ(FirstChar) to Ch do
  307.               Include(aToken^.bpnCharClass, LowerCaseChar(ChInx));
  308.             RangeState := Completed;
  309.           end
  310.           else begin
  311.             Include(aToken^.bpnCharClass, Ch);
  312.             FirstChar := Ch;
  313.             RangeState := CouldStart;
  314.           end;
  315.         end;
  316.  
  317.       c_frxNegate :
  318.         begin
  319.           {the class negation can only be the first character}
  320.           if (aInx <> FirstInx) then
  321.             Exit;
  322.           {make a note that we have a negated class}
  323.           NegatedClass := true;
  324.           {advance the first character, in effect ignoring the
  325.            negation metacharacter}
  326.           inc(FirstInx);
  327.         end;
  328.  
  329.       c_frxClassRight :
  330.         begin
  331.           {the right bracket cannot be the first character}
  332.           if (aInx = FirstInx) then
  333.             Exit;
  334.           {make a note that we found the right bracket and break out
  335.            of the loop}
  336.           FoundRightBracket := true;
  337.           Break;
  338.         end;
  339.  
  340.       c_frxClassRange :
  341.         begin
  342.           {if this is the first character in the class then it's a
  343.            literal character}
  344.           if (aInx = FirstInx) then
  345.             Include(aToken^.bpnCharClass, c_frxClassRange)
  346.           {otherwise it's a range character, so we must be able to
  347.            start a range}
  348.           else if (RangeState <> CouldStart) then
  349.             Exit
  350.           {make a note that we're in a range}
  351.           else
  352.             RangeState := Started;
  353.         end;
  354.     else
  355.       {it's a literal character; there are two cases, it's the end of
  356.        a range or it isn't}
  357.       if (RangeState = Started) then begin
  358.         if (Ch <= FirstChar) then
  359.           Exit;
  360.         for ChInx := succ(FirstChar) to Ch do
  361.           Include(aToken^.bpnCharClass, LowerCaseChar(ChInx));
  362.         RangeState := Completed;
  363.       end
  364.       else begin
  365.         Include(aToken^.bpnCharClass, Ch);
  366.         FirstChar := Ch;
  367.         RangeState := CouldStart;
  368.       end;
  369.     end;{case}
  370.   end;
  371.  
  372.   {do a final check on everything being OK (ie, we found a right
  373.    bracket and we're not in the middle of parsing a range)}
  374.   if (not FoundRightBracket) or (RangeState = Started) then
  375.     Exit;
  376.  
  377.   {if the class is negated then negate the definition}
  378.   if NegatedClass then
  379.     NegateSet(aToken^.bpnCharClass);
  380.  
  381.   {all's well}
  382.   Result := frxcrSuccess;
  383. end;
  384. {====================================================================}
  385.  
  386.  
  387. {===Interfaced routines==============================================}
  388. procedure FRXFreeBinPattern(var aBinPattern : PfrxBinPattern);
  389. begin
  390.   if (aBinPattern <> nil) then begin
  391.     with PBinPatHeader(aBinPattern)^ do
  392.       FreeMem(bphData, bphSize);
  393.     FreeMem(aBinPattern, sizeof(TBinPatHeader));
  394.     aBinPattern := nil;
  395.   end;
  396. end;
  397. {--------}
  398. function FRXCompilePattern(const aPattern    : string;
  399.                              var aBinPattern : PfrxBinPattern) : TfrxCompileResult;
  400. var
  401.   Ch     : char;
  402.   Inx    : integer;
  403.   PatLen : integer;
  404.   Token  : PBinPatNode;
  405. begin
  406.   {assume success}
  407.   Result := frxcrSuccess;
  408.  
  409.   {start the binary pattern off}
  410.   aBinPattern := nil;
  411.  
  412.   {an empty pattern string is invalid}
  413.   PatLen := length(aPattern);
  414.   if (PatLen = 0) then begin
  415.     Result := frxcrNoPattern;
  416.     Exit;
  417.   end;
  418.  
  419.   {wander through the pattern string character by character}
  420.   Inx := 0;
  421.   while (Inx < PatLen) do begin
  422.     inc(Inx);
  423.  
  424.     {look for a metacharacter}
  425.     Ch := LowerCaseChar(aPattern[Inx]);
  426.     case Ch of
  427.  
  428.       {for ? and * just create a new pattern token}
  429.       c_frxAnyChar :
  430.         begin
  431.           AllocPatternNode(aBinPattern, c_binAnyChar);
  432.         end;
  433.       c_frxClosure :
  434.         begin
  435.           AllocPatternNode(aBinPattern, c_binAnyClosure);
  436.         end;
  437.  
  438.       {with the subpattern closure, there must be a prior subpattern
  439.        that's not already closed}
  440.       c_frxPatClosure :
  441.         begin
  442.           if not CloseLastPatternToken(aBinPattern) then begin
  443.             FRXFreeBinPattern(aBinPattern);
  444.             Result := frxcrNoSubpattern;
  445.             Exit;
  446.           end;
  447.         end;
  448.  
  449.       {the escape character cannot appear at the end of the pattern}
  450.       c_frxEscape :
  451.         begin
  452.           if (Inx = PatLen) then begin
  453.             FRXFreeBinPattern(aBinPattern);
  454.             Result := frxcrMissingChar;
  455.             Exit;
  456.           end;
  457.           Token := AllocPatternNode(aBinPattern, c_binLiteral);
  458.           inc(Inx);
  459.           Token^.bpnChar := LowerCaseChar(aPattern[Inx]);
  460.         end;
  461.  
  462.       {fun one: the left bracket at the start of a character class}
  463.       c_frxClassLeft :
  464.         begin
  465.           {it can't appear at the end of the pattern}
  466.           if (Inx = PatLen) then begin
  467.             FRXFreeBinPattern(aBinPattern);
  468.             Result := frxcrBadClass;
  469.             Exit;
  470.           end;
  471.           {create a new token as if everything was OK}
  472.           Token := AllocPatternNode(aBinPattern, c_binClass);
  473.           {parse the character class}
  474.           Result := ParseCharClass(aPattern, PatLen, Inx, Token);
  475.           if (Result <> frxcrSuccess) then begin
  476.             FRXFreeBinPattern(aBinPattern);
  477.             Exit;
  478.           end;
  479.         end;
  480.  
  481.       {the right bracket cannot appear without a left one}
  482.       c_frxClassRight :
  483.         begin
  484.           FRXFreeBinPattern(aBinPattern);
  485.           Result := frxcrMissingLeft;
  486.           Exit;
  487.         end;
  488.  
  489.     else
  490.       {any other character is a literal}
  491.       Token := AllocPatternNode(aBinPattern, c_binLiteral);
  492.       Token^.bpnChar := Ch;
  493.     end;{case}
  494.   end;
  495. end;
  496. {--------}
  497. function FRXMatchesPattern(aBinPattern : PfrxBinPattern;
  498.                      const aFileName   : string) : boolean;
  499. type
  500.   TCheckPoint = packed record
  501.     cpToken : PBinPatNode;
  502.     cpStart : word;
  503.     cpInx   : word;
  504.   end;
  505. var
  506.   FNLen   : integer;
  507.   Inx     : integer;
  508.   StartInx: integer;
  509.   Token   : PBinPatNode;
  510.   BadSimpleMatch : boolean;
  511.   TokenSP : integer;
  512.   TokenStack : array [0..127] of TCheckPoint;
  513. begin
  514.   {assume that we'll fail}
  515.   Result := false;
  516.  
  517.   {if the pattern is empty, there's no match}
  518.   if (aBinPattern = nil) then
  519.     Exit;
  520.  
  521.   {if the filename is the empty string, there's no match}
  522.   FNLen := length(aFileName);
  523.   if (FNLen = 0) then
  524.     Exit;
  525.  
  526.   {prepare closure token stack to be empty}
  527.   TokenSP := -1;
  528.  
  529.   {prepare for loop}
  530.   Token := PBinPatNode(PBinPatHeader(aBinPattern)^.bphData);
  531.   Inx := 1;
  532.   while True do begin
  533.     BadSimpleMatch := false;
  534.     case Token^.bpnToken of
  535.       c_binAnyClosure :
  536.         begin
  537.           {push it onto the stack as a greedy token}
  538.           inc(TokenSP);
  539.           with TokenStack[TokenSP] do begin
  540.             cpToken := Token;
  541.             cpStart := Inx;
  542.             cpInx := succ(FNLen);
  543.           end;
  544.           {indicate we've matched everything}
  545.           Inx := succ(FNLen);
  546.           {advance the token}
  547.           Token := Token^.bpnNext;
  548.         end;
  549.       c_binLitClosure,
  550.       c_binClsClosure :
  551.         begin
  552.           {match as many chars as we can}
  553.           StartInx := Inx;
  554.           while (Inx <= FNLen) and
  555.                 MatchOneChar(Token, aFileName[Inx]) do
  556.             inc(Inx);
  557.           {if we matched at least one char...}
  558.           if (StartInx < Inx) then begin
  559.             {push it onto the stack as a greedy token}
  560.             inc(TokenSP);
  561.             with TokenStack[TokenSP] do begin
  562.               cpToken := Token;
  563.               cpStart := StartInx;
  564.               cpInx := Inx;
  565.             end;
  566.           end;
  567.           {advance the token}
  568.           Token := Token^.bpnNext;
  569.         end;
  570.     else {the current token is a simple token}
  571.       {if there is a current character and it matches the current
  572.        token, advance}
  573.       if (Inx <= FNLen) and
  574.          MatchOneChar(Token, aFileName[Inx]) then begin
  575.         Token := Token^.bpnNext;
  576.         inc(Inx);
  577.       end
  578.       {otherwise there is no current character or it did not match}
  579.       else begin
  580.         {if there is no closure to revert to, we're done but failed}
  581.         if (TokenSP = -1) then
  582.           Exit;
  583.         {make a note we failed to match: this'll trigger an operation
  584.          at the end of the loop to revert to a previous closure}
  585.         BadSimpleMatch := true;
  586.       end;
  587.     end;{case}
  588.  
  589.     {we're finished and successful if the current token is nil (ie, we
  590.      ran out of tokens) and the current character index is greater
  591.      than the length of the string (ie, we ran out of string)}
  592.     if (Token = nil) and (Inx > FNLen) then begin
  593.       Result := true;
  594.       Exit;
  595.     end;
  596.  
  597.     {if the current token is nil or there was a bad simple match, we
  598.      need to revert to a previous closure and back up one character}
  599.     if (Token = nil) or BadSimpleMatch then begin
  600.       while (TokenSP <> -1) do begin
  601.         with TokenStack[TokenSP] do begin
  602.           if (cpInx > cpStart) then begin
  603.             dec(cpInx);
  604.             Token := cpToken^.bpnNext;
  605.             Inx := cpInx;
  606.             Break;{out of while loop}
  607.           end;
  608.           dec(TokenSP);
  609.         end;
  610.       end;
  611.       {if there are no more closures or the current token is still
  612.        nil, we're done but failed}
  613.       if (TokenSP = -1) or (Token = nil) then
  614.         Exit;
  615.     end;
  616.   end;{of forever loop}
  617. end;
  618. {--------}
  619. {$IFDEF Debug}
  620. procedure FRXPrintBinPattern(var aFile       : text;
  621.                            const aPattern    : string;
  622.                                  aBinPattern : PfrxBinPattern);
  623. var
  624.   Ch   : char;
  625.   Temp : PBinPatNode;
  626. begin
  627.   writeln(aFile, 'Binary pattern print of "', aPattern, '"');
  628.   Temp := PBinPatNode(PBinPatHeader(aBinPattern)^.bphData);
  629.   while (Temp <> nil) do begin
  630.     case Temp^.bpnToken of
  631.       c_binAnyChar    : writeln(aFile, '<any char>    ');
  632.       c_binAnyClosure : writeln(aFile, '<closure>     ');
  633.       c_binLiteral    : writeln(aFile, '<literal>     [', Temp^.bpnChar, ']');
  634.       c_binLitClosure : writeln(aFile, '<lit.closure> [', Temp^.bpnChar, ']');
  635.       c_binClass,
  636.       c_binClsClosure : begin
  637.                           if (Temp^.bpnToken = c_binClass) then
  638.                             write(aFile, '<char class>  ')
  639.                           else
  640.                             write(aFile, '<classclosure>');
  641.                           for Ch := #0 to #63 do begin
  642.                             if Ch in Temp^.bpnCharClass then
  643.                               write(aFile, '+')
  644.                             else
  645.                               write(aFile, '.');
  646.                           end;
  647.                           for Ch := #64 to #255 do begin
  648.                             if ((ord(Ch) mod 64) = 0) then begin
  649.                               writeln(aFile);
  650.                               write(aFile, '              ');
  651.                             end;
  652.                             if Ch in Temp^.bpnCharClass then
  653.                               write(aFile, '+')
  654.                             else
  655.                               write(aFile, '.');
  656.                           end;
  657.                           writeln(aFile);
  658.                         end;
  659.     else
  660.       writeln(aFile, '***unknown token***');
  661.     end;{case}
  662.     Temp := Temp^.bpnNext;
  663.   end;
  664.   writeln(aFile, '---');
  665. end;
  666. {$ENDIF}
  667. {====================================================================}
  668.  
  669. end.
  670.